Lambda運算式和函式介面是Java 8引入的重要特性,為Java程式設計帶來革命性的變化。
這兩個特性的引入使得Java在函數式程式設計方面邁出重要的一步,可以簡化程式碼,提高可讀性,並行處理和集合操作,特別是在處理集合、多執行緒程式設計和事件驅動程式設計等場景中。
函式介面是Java 8引入的一個重要概念,為Lambda運算式提供類型。
函式介面(Function Interface)是只包含一個抽象方法的介面。這個單一的抽象方法定義該介面的功能契約。
函式介面(Function Interface)可以包含多個預設方法或靜態方法,但只能有一個抽象方法。
Java提供@FunctionalInterface註解來標記函式介面。
這個註解不是強制性的,可以幫助編譯器檢查該介面是否符合函式介面的要求。
如果一個被@FunctionalInterface標記的介面包含多個抽象方法,編譯器會報錯。
例如:
@FunctionalInterface
public interface Executable {
void execute();
}
Java 8在java.util.function包中提供許多內建的函式介面,以下是一些常用的例子:
Predicate:接受一個輸入參數,返回一個布林值結果。
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<T, R>:接受一個輸入參數,產生一個結果。
Function<String, Integer> stringLength = s -> s.length();
Consumer:接受一個輸入參數,不返回結果。
Consumer<String> printOut = s -> System.out.println(s);
Supplier:不接受參數,產生一個結果。
Supplier<Double> randomNumber = () -> Math.random();
以下是統整的程式碼:
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class FunctionalInterfacesExample {
public static void main(String[] args) {
// 1. Predicate<T>
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println("Is 4 even? " + isEven.test(4));
System.out.println("Is 7 even? " + isEven.test(7));
// 2. Function<T, R>
Function<String, Integer> stringLength = s -> s.length();
System.out.println("Length of 'Hello': " + stringLength.apply("Hello"));
// 3. Consumer<T>
Consumer<String> printOut = s -> System.out.println(s);
printOut.accept("This is a test message");
// 4. Supplier<T>
Supplier<Double> randomNumber = () -> Math.random();
System.out.println("Random number: " + randomNumber.get());
}
}
Lambda運算式提供簡潔的方式來表示匿名函式。
Lambda運算式的基本語法如下:
(參數列表) -> { 表達式主體 }
參數列表可以為空,也可以包含一個或多個參數。
當只有一個參數時,可以省略括號。例如:
// 無參數
() -> System.out.println("Hello, Lambda!");
// 單一參數
x -> x * x
// 多個參數
(x, y) -> x + y
箭頭運算子 ->
將參數列表與Lambda主體分隔開來,可以理解為「變成」或「產生」。
表達式主體可以是一個表達式或一個語句塊。如果是單一表達式,可以省略大括號和return關鍵字。例如:
// 單一表達式
x -> x * x
// 語句塊
(x, y) -> {
int sum = x + y;
return sum;
}
讓我們看一些實際的Lambda運算式範例:
Runnable task = () -> System.out.println("Executing task");
new Thread(task).start();
List<String> names = Arrays.asList("Zhang", "Li", "Wang");
Collections.sort(names, (a, b) -> a.length() - b.length());
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
以下是統整的程式碼:
import java.util.*;
import java.util.stream.Collectors;
public class LambdaExamples {
public static void main(String[] args) {
// 1. Using Runnable interface
Runnable task = () -> System.out.println("Executing task");
new Thread(task).start();
// 2. Using Comparator for sorting
List<String> names = Arrays.asList("Zhang", "Li", "Wang");
Collections.sort(names, (a, b) -> a.length() - b.length());
System.out.println("Sorted names: " + names);
// 3. Using Predicate for filtering
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
}
}
Lambda運算式在Java中有廣泛的應用,特別是在處理集合、事件處理和多執行緒程式設計等場景中。讓我們來看看一些常見的使用場景。
Lambda運算式與Stream API結合,可以大大簡化集合的操作。
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
以下是統整的程式碼:
import java.util.*;
import java.util.stream.Collectors;
public class LambdaStreamExample {
public static void main(String[] args) {
// Initialize the list of names
List<String> names = Arrays.asList("Zhang", "Li", "Wang", "Zhao");
// 1. Filtering collection
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
System.out.println("Names longer than 3 characters: " + longNames);
// 2. Transforming collection
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Names in uppercase: " + upperCaseNames);
}
}
在圖形用戶界面(GUI)程式設計中,Lambda運算式可以簡化事件監聽器的實現。
button.addActionListener(e -> System.out.println("按鈕被點擊!"));
Lambda運算式使得創建和使用執行緒(thread)變得更加簡單。
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("執行緒運行中: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
System.out.println("任務正在執行緒池中運行");
});
executor.shutdown();
方法參考是Lambda運算式的一種簡化形式,來表示只調用一個方法的Lambda運算式。
方法參考使用雙冒號 ::
運算子,讓我們來看看三種主要的方法參考類型。
靜態方法參考指向類別的靜態方法。語法為 類別名::靜態方法名
。
例如:
numbers.stream()
.map(Math::sqrt)
.forEach(System.out::println);
Math::sqrt
是對 Math.sqrt()
方法的參考。
以下是統整的程式碼:
import java.util.Arrays;
import java.util.List;
public class StreamSqrtExample {
public static void main(String[] args) {
// Initialize the list of numbers
List<Integer> numbers = Arrays.asList(1, 4, 9, 16);
// Calculate and print the square root of each number
System.out.println("Square roots of numbers:");
numbers.stream()
.map(Math::sqrt)
.forEach(System.out::println);
}
}
實體方法參照指向物件的實體方法。
語法為 物件::實例方法名
。
例如:
Predicate<String> startsWithH = text::startsWith;
以下是統整的程式碼:
import java.util.function.Predicate;
public class MethodReferenceExample {
public static void main(String[] args) {
String text = "Hello, World!";
// Create a Predicate using method reference
Predicate<String> startsWithH = text::startsWith;
// Test the Predicate
System.out.println("Does 'Hello' start with 'H'? " + startsWithH.test("H")); // Output: true
System.out.println("Does 'World' start with 'H'? " + startsWithH.test("W")); // Output: false
// Additional examples to showcase more uses
Predicate<String> endsWithExclamation = text::endsWith;
System.out.println("Does the text end with '!'? " + endsWithExclamation.test("!")); // Output: true
Predicate<String> contains = text::contains;
System.out.println("Does the text contain 'World'? " + contains.test("World")); // Output: true
}
}
建構子參考用於創建物件。
語法為 類別名::new
。
例如:
Supplier<List>> listFactory = ArrayList::new;
以下是統整的程式碼:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class ConstructorReferenceExample {
public static void main(String[] args) {
// Create a Supplier using constructor reference
Supplier<List>> listFactory = ArrayList::new;
// Use the Supplier to create a new ArrayList
List<String> list = listFactory.get();
// Add some elements to the list
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// Print the list
System.out.println("Created list: " + list);
// Demonstrate another use of constructor reference
Supplier<String> stringFactory = String::new;
String emptyString = stringFactory.get();
System.out.println("Empty string length: " + emptyString.length());
// Use constructor reference with parameters
java.util.function.Function<String, StringBuilder> sbFactory = StringBuilder::new;
StringBuilder sb = sbFactory.apply("Hello, Constructor Reference!");
System.out.println("StringBuilder content: " + sb);
}
}
Stream API是Java 8引入的另一個重要特性,與Lambda運算式一同使用,為處理集合提供強大而靈活的工具。
Stream代表元素的序列,可以進行各種操作,不是一個數據結構,而是一個用於處理數據的工具。
Stream操作可以是中間操作(返回Stream)或終端操作(產生結果)。
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
List<Integer> distinctNumbers = numbersWithDuplicates.stream()
.distinct()
.collect(Collectors.toList());
List<Integer> limitedNumbers = numbers.stream()
.limit(5)
.collect(Collectors.toList());
List<Integer> skippedNumbers = numbers.stream()
.skip(5)
.collect(Collectors.toList());
以下是統整的程式碼:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamOperationsExample {
public static void main(String[] args) {
// 1. filter: Filter elements
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even numbers: " + evenNumbers);
// 2. map: Transform elements
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Name lengths: " + nameLengths);
// 3. reduce: Combine elements of the stream
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println("Sum of numbers: " + sum);
// 4. sorted: Sort elements
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted names: " + sortedNames);
// Additional examples
// 5. distinct: Remove duplicates
List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 3, 4, 5, 5);
List<Integer> distinctNumbers = numbersWithDuplicates.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Distinct numbers: " + distinctNumbers);
// 6. limit: Limit the number of elements
List<Integer> limitedNumbers = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println("First 5 numbers: " + limitedNumbers);
// 7. skip: Skip elements
List<Integer> skippedNumbers = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println("Numbers after skipping first 5: " + skippedNumbers);
}
}
Lambda運算式在Stream操作中扮演著關鍵角色,Stream API結合Lambda運算式提供聲明式的程式設計方式,使得複雜的數據處理變得簡單直觀。
例如,我們可以使用Lambda來實現複雜的過濾和轉換:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ComplexStreamExample {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("John", 25),
new Person("Alice", 30),
new Person("Bob", 35)
);
List<String> result = personList.stream()
.filter(p -> p.getAge() > 28)
.map(Person::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Names of people older than 28 (in uppercase): " + result);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
我們使用Lambda運算式來過濾年齡大於28的人,然後將他們的名字轉換為大寫。
雖然Java提供許多內建的函式介面,但有時我們可能需要創建自定義的函式介面來滿足特定的需求。
創建自定義函式介面非常簡單,只需定義一個包含單一抽象方法的介面即可。
我們可以使用 @FunctionalInterface 註解來確保介面符合函式介面的要求。
例如,讓我們創建一個自定義的數學運算介面:
@FunctionalInterface
public interface MathOperation {
int calculate(int a, int b);
}
一旦我們定義自定義函式介面,就可以使用Lambda運算式來實現他。以下是一些使用我們剛剛定義的 MathOperation 介面的例子:
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
// Addition
MathOperation addition = (a, b) -> a + b;
System.out.println("10 + 5 = " + operate(10, 5, addition));
// Subtraction
MathOperation subtraction = (a, b) -> a - b;
System.out.println("10 - 5 = " + operate(10, 5, subtraction));
// Multiplication
MathOperation multiplication = (a, b) -> a * b;
System.out.println("10 * 5 = " + operate(10, 5, multiplication));
// Division
MathOperation division = (a, b) -> {
if (b == 0) throw new ArithmeticException("Cannot divide by zero");
return a / b;
};
System.out.println("10 / 5 = " + operate(10, 5, division));
// Custom operation (e.g., power)
MathOperation power = (a, b) -> (int) Math.pow(a, b);
System.out.println("10^2 = " + operate(10, 2, power));
}
private static int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.calculate(a, b);
}
}
在大多數情況下,Lambda運算式的效能與等效的匿名內部類別相當或更好。這是因為:
例如:
// 使用匿名內部類別
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
// 使用Lambda運算式
Runnable r2 = () -> System.out.println("Hello World");
在這個例子中,r2 的實現通常會比 r1 更高效。
Lambda運算式通常比匿名內部類別使用更少的記憶體,因為:
如果Lambda捕獲許多變數,可能會導致更高的記憶體使用。
Java編譯器和JVM會對Lambda運算式進行各種優化,包括:
儘管Lambda運算式在大多數情況下都能提供良好的效能,但在處理大量數據或高頻率操作時,仍然需要進行效能測試和優化。在某些極端情況下,傳統的for循環可能比使用Stream和Lambda更高效。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class PerformanceComparisonTest {
private static final int LIST_SIZE = 10_000_000;
private static final int ITERATIONS = 10;
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(LIST_SIZE);
for (int i = 0; i < LIST_SIZE; i++) {
numbers.add(i);
}
// Warm-up
for (int i = 0; i < 5; i++) {
testStreamLambda(numbers);
testTraditionalLoop(numbers);
}
// Actual test
long streamLambdaTime = 0;
long traditionalLoopTime = 0;
for (int i = 0; i < ITERATIONS; i++) {
streamLambdaTime += testStreamLambda(numbers);
traditionalLoopTime += testTraditionalLoop(numbers);
}
System.out.println("Average time for Stream and Lambda: " + (streamLambdaTime / ITERATIONS) + " ms");
System.out.println("Average time for Traditional Loop: " + (traditionalLoopTime / ITERATIONS) + " ms");
}
private static long testStreamLambda(List<Integer> numbers) {
long start = System.currentTimeMillis();
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.collect(Collectors.toList());
long end = System.currentTimeMillis();
return end - start;
}
private static long testTraditionalLoop(List<Integer> numbers) {
long start = System.currentTimeMillis();
List<Integer> result = new ArrayList<>();
for (Integer n : numbers) {
if (n % 2 == 0) {
result.add(n * 2);
}
}
long end = System.currentTimeMillis();
return end - start;
}
}
在使用Lambda運算式和函式介面時,以下是一些建議和注意事項:
保持簡潔:Lambda應該簡短明瞭。如果Lambda體變得複雜,考慮提取為一個方法。
// 好的做法
list.forEach(item -> System.out.println(item));
// 當邏輯複雜時,提取為方法
list.forEach(this::processItem);
使用方法引用:當Lambda只是調用一個已存在的方法時,使用方法引用。
// 使用Lambda
list.forEach(s -> System.out.println(s));
// 使用方法引用(更佳)
list.forEach(System.out::println);
明確的參數類型:在某些情況下,顯式指定參數類型可以提高可讀性。
list.stream().map((String s) -> s.toLowerCase());
使用 Lambda 表達式時,需要特別注意 NullPointerException 的潛在風,可以利用 Java 8 引入的 Optional 類。例如:
Optional.ofNullable(someObject)
.map(obj -> obj.getSomeValue())
.orElse("Default Value");
在多線程環境中使用 Lambda 表達式時,也有多種方法可供選擇,如 CompletableFuture 或 parallel streams。然而,這些高級特性也帶來了一些潛在的陷阱,需要謹慎使用。例如,使用 parallel streams 時要注意線程安全問題:
list.parallelStream()
.filter(item -> item.isValid())
.forEach(item -> processItem(item)); // 確保 processItem 是線程安全的
Stream API 的使用方式與 SQL 查詢有相似之處,都提供了聲明式的數據處理方法。
employees.stream()
.filter(e -> e.getDepartment().equals("IT"))
.map(Employee::getSalary)
.average()
.orElse(0.0);
類似於 SQL 中的
SELECT AVG(salary) FROM employees WHERE department = 'IT'。
本篇文章同步刊載: JYI.TW
筆者個人的網站: JUNYI